package com.moneydance.modules.features.myposition;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.imageio.ImageIO;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JEditorPane;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.border.EmptyBorder;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.PiePlot;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.data.general.PieDataset;
import org.jfree.data.time.Day;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;

import com.moneydance.apps.md.model.AbstractTxn;
import com.moneydance.apps.md.model.Account;
import com.moneydance.apps.md.model.Budget;
import com.moneydance.apps.md.model.BudgetItem;
import com.moneydance.apps.md.model.BudgetList;
import com.moneydance.apps.md.model.ExpenseAccount;
import com.moneydance.apps.md.model.IncomeAccount;
import com.moneydance.apps.md.model.RootAccount;
import com.moneydance.apps.md.model.TransactionSet;
import com.moneydance.awt.AwtUtil;

/** My Position Window.
 * Stuff you need to know
 * */
public class MyPositionWindow extends JFrame {

    private Main extension;
	private String spendingAccountName;
	private Date startDate;
	private Date endDate;
	private Date today = DateUtil.setTimeZero(new Date());
	private Account spendingAccount = null;
	private boolean includeDayByDayBalance = false;

	// Panel components
	private JEditorPane txtMainSummary;
	private JEditorPane txtReport;
	private JEditorPane txtIncomes;
	private JEditorPane txtExpenses;
	private JButton printButton;
	private JButton saveButton;
	private JButton closeButton;

	// Formats
	public static final DecimalFormat CURR_FMT = new DecimalFormat("#,##0");
	public static final DecimalFormat CENTS_FMT = new DecimalFormat("00");
	public static final SimpleDateFormat DT_FMT = new SimpleDateFormat("yyyy-MM-dd");
	public static final SimpleDateFormat DT_FMT2 = new SimpleDateFormat("EEE yyyy-MM-dd");
	
	public static final int START_DAY_OF_WEEK = Calendar.MONDAY;
	
	public static final int INCOME_ACCOUNTS = 0;
	public static final int EXPENSE_ACCOUNTS = 1;
	public static final int DIFF_ACCOUNTS = 2; // Income - Expense
	
	/** Report Object */
	private ReportObj reportObj = null;
	
	private long spendingAccountStartBalance = 0;
	
	// Size of charts
	public static final int WINDOW_WIDTH = 800;
	public static final int WINDOW_HEIGHT = 600;
	public static final int WINDOW_EDITPANE_WIDTH = WINDOW_WIDTH-30;
	public static final int WINDOW_CHART_WIDTH = WINDOW_WIDTH-30;
	public static final int WINDOW_BALANCE_HEIGHT = 320;
	public static final int WINDOW_PIE_HEIGHT = 350;

	// Charts
    private JFreeChart balanceChart = null;
    private ChartPanel balanceChartPanel = null;
    private JFreeChart incomeChart = null;
    private ChartPanel incomeChartPanel = null;
    private JFreeChart incomeActualPieChart = null;
    private ChartPanel incomeActualPieChartPanel = null;
    private JFreeChart incomeBudgetPieChart = null;
    private ChartPanel incomeBudgetPieChartPanel = null;
    private JFreeChart expenseChart = null;
    private ChartPanel expenseChartPanel = null;
    private JFreeChart expenseActualPieChart = null;
    private ChartPanel expenseActualPieChartPanel = null;
    private JFreeChart expenseBudgetPieChart = null;
    private ChartPanel expenseBudgetPieChartPanel = null;
    private JFreeChart expenseAllBudgetPieChart = null;
    private ChartPanel expenseAllBudgetPieChartPanel = null;

	
	// -------------------------------------------

	/**
	 * @param extension
	 * @param spendingAccountName
	 * @param budgetPeriod
	 * @param startDate
	 * @param endDate
	 * @param includeDayByDayBalance
	 */
	public MyPositionWindow(Main extension, String spendingAccountName, String budgetPeriod,
			Date startDate, Date endDate,boolean includeDayByDayBalance) {
	    super("My Position Report");
	    System.out.println("My Position Report");
//	    System.out.println("startDate="+startDate+" endDate="+endDate);
	    this.extension = extension;
	    this.spendingAccountName = spendingAccountName;
	    this.startDate = DateUtil.setTimeZero(startDate);
	    this.endDate = DateUtil.setTimeZero(endDate);
	    RootAccount root = extension.getUnprotectedContext().getRootAccount();
	    this.spendingAccount = root.getAccountByName(spendingAccountName);
	    this.spendingAccountStartBalance = getSpendingAccountBalance(DateUtil.addDays(startDate,-1));
	    this.includeDayByDayBalance = includeDayByDayBalance;
	    
	    // Get a list of all categories
		getReportObj();

	    JPanel p = new JPanel(new GridBagLayout());
	    p.setBorder(new EmptyBorder(5,5,5,5));
	    // X and y for layout
	    int x = 0;
	    int y = 0;
	    int sx = 0;
	    int sy = 0;
	    // Scroll Panel
	    JPanel sp = new JPanel(new GridBagLayout());
	    sp.setBorder(new EmptyBorder(0,0,0,0));
	    
	    // Main Summary Text Area
	    txtMainSummary = new MyEditorPane(WINDOW_EDITPANE_WIDTH);
	    txtMainSummary.setEditable(false);
	    txtMainSummary.setContentType("text/html");
	    txtMainSummary.setText(getMainSummaryStr());
	    sp.add(txtMainSummary, AwtUtil.getConstraints(sx,sy++,1,1,1,1,false,false));

	    // Main Balance Chart
        final TimeSeriesCollection balanceDataset = createBalanceDataset();
        balanceChart = createBalanceChart(balanceDataset,"Main Spending Account Balance");
        balanceChartPanel = new ChartPanel(balanceChart);
        balanceChartPanel.setPreferredSize(new java.awt.Dimension(WINDOW_CHART_WIDTH, WINDOW_BALANCE_HEIGHT));
	    sp.add(balanceChartPanel, AwtUtil.getConstraints(sx,sy++,1,1,1,1,false,false));

	    // Income Text Area
	    txtIncomes = new MyEditorPane(WINDOW_EDITPANE_WIDTH);
	    txtIncomes.setEditable(false);
	    txtIncomes.setContentType("text/html");
	    txtIncomes.setText(getIncomeListStr());
	    sp.add(txtIncomes, AwtUtil.getConstraints(sx,sy++,1,1,1,1,false,false));

	    // Income Balance Chart
	    final TimeSeriesCollection incomeDataset = createIncomeDataset();
	    incomeChart = createBalanceChart(incomeDataset,"Income Only");
	    incomeChartPanel = new ChartPanel(incomeChart);
	    incomeChartPanel.setPreferredSize(new java.awt.Dimension(WINDOW_CHART_WIDTH, WINDOW_BALANCE_HEIGHT));
	    sp.add(incomeChartPanel, AwtUtil.getConstraints(sx,sy++,1,1,1,1,false,false));
	    
	    // Income Actual Pie Chart
	    PieDataset incomeActualDS = createIncomeExpenseDataset(reportObj.incomeActualItems, reportObj.incomeActualTotal,true);
	    incomeActualPieChart = createIncomeExpensePieChart(incomeActualDS,"My Actual Income");
	    incomeActualPieChartPanel = new ChartPanel(incomeActualPieChart);
	    incomeActualPieChartPanel.setPreferredSize(new java.awt.Dimension(WINDOW_CHART_WIDTH, WINDOW_BALANCE_HEIGHT));
	    sp.add(incomeActualPieChartPanel, AwtUtil.getConstraints(sx,sy++,1,1,1,1,false,false));
	    
	    // Income Budget Pie Chart
	    PieDataset incomeBudgetDS = createIncomeExpenseDataset(reportObj.incomeBudgetItems, reportObj.incomeBudgetTotal,true);
	    incomeBudgetPieChart = createIncomeExpensePieChart(incomeBudgetDS,"My Budgeted Income");
	    incomeBudgetPieChartPanel = new ChartPanel(incomeBudgetPieChart);
	    incomeBudgetPieChartPanel.setPreferredSize(new java.awt.Dimension(WINDOW_CHART_WIDTH, WINDOW_BALANCE_HEIGHT));
	    sp.add(incomeBudgetPieChartPanel, AwtUtil.getConstraints(sx,sy++,1,1,1,1,false,false));
	    
	    // Expense Text Area
	    txtExpenses = new MyEditorPane(WINDOW_EDITPANE_WIDTH);
	    txtExpenses.setEditable(false);
	    txtExpenses.setContentType("text/html");
	    txtExpenses.setText(getExpenseListStr());
	    sp.add(txtExpenses, AwtUtil.getConstraints(sx,sy++,1,1,1,1,false,false));
	    
	    // Expense Balance Chart
	    final TimeSeriesCollection expenseDataset = createExpenseDataset();
	    expenseChart = createBalanceChart(expenseDataset,"Expense Only");
	    expenseChartPanel = new ChartPanel(expenseChart);
	    expenseChartPanel.setPreferredSize(new java.awt.Dimension(WINDOW_CHART_WIDTH, WINDOW_BALANCE_HEIGHT));
	    sp.add(expenseChartPanel, AwtUtil.getConstraints(sx,sy++,1,1,1,1,false,false));
	    
	    // Expense Actual Pie Chart to today
	    PieDataset expenseActualDS = createIncomeExpenseDataset(reportObj.expenseActualItems, reportObj.expenseActualTotal,false);
	    expenseActualPieChart = createIncomeExpensePieChart(expenseActualDS,"My Actual Expenses to today");
	    expenseActualPieChartPanel = new ChartPanel(expenseActualPieChart);
	    expenseActualPieChartPanel.setPreferredSize(new java.awt.Dimension(WINDOW_CHART_WIDTH, WINDOW_PIE_HEIGHT));
	    sp.add(expenseActualPieChartPanel, AwtUtil.getConstraints(sx,sy++,1,1,1,1,false,false));
	    
	    // Expense Budget Pie Chart to today
	    PieDataset expenseBudgetDS = createIncomeExpenseDataset(reportObj.expenseBudgetItems, reportObj.expenseBudgetTotal,false);
	    expenseBudgetPieChart = createIncomeExpensePieChart(expenseBudgetDS,"My Budgeted Expenses to today");
	    expenseBudgetPieChartPanel = new ChartPanel(expenseBudgetPieChart);
	    expenseBudgetPieChartPanel.setPreferredSize(new java.awt.Dimension(WINDOW_CHART_WIDTH, WINDOW_PIE_HEIGHT));
	    sp.add(expenseBudgetPieChartPanel, AwtUtil.getConstraints(sx,sy++,1,1,1,1,false,false));
	    
	    // Expense Budget Pie Chart ALL
	    PieDataset expenseAllBudgetDS = createIncomeExpenseDataset(reportObj.expenseAllBudgetItems, reportObj.expenseAllBudgetTotal,false);
	    expenseAllBudgetPieChart = createIncomeExpensePieChart(expenseAllBudgetDS,"All Budgeted Expenses");
	    expenseAllBudgetPieChartPanel = new ChartPanel(expenseAllBudgetPieChart);
	    expenseAllBudgetPieChartPanel.setPreferredSize(new java.awt.Dimension(WINDOW_CHART_WIDTH, WINDOW_PIE_HEIGHT));
	    sp.add(expenseAllBudgetPieChartPanel, AwtUtil.getConstraints(sx,sy++,1,1,1,1,false,false));

	    if (this.includeDayByDayBalance) {
	    	// Text Area
	    	txtReport = new MyEditorPane(WINDOW_EDITPANE_WIDTH);
	    	txtReport.setEditable(false);
	    	txtReport.setContentType("text/html");
	    	txtReport.setText(getDayByDayReportStr());
	    	sp.add(txtReport, AwtUtil.getConstraints(sx,sy++,1,1,1,1,false,false));
	    }
	    
	    // Add ScrollPane
	    JScrollPane scrollPane = new JScrollPane(sp);
	    scrollPane.setMaximumSize(new Dimension(WINDOW_WIDTH-20, Integer.MAX_VALUE));
	    scrollPane.setPreferredSize(new Dimension(WINDOW_WIDTH-20,2000));
	    scrollPane.getVerticalScrollBar().setUnitIncrement(30);
	    p.add(scrollPane, AwtUtil.getConstraints(x,y++,1,1,4,1,true,true));
	    p.add(Box.createVerticalStrut(8), AwtUtil.getConstraints(x,y++,0,0,1,1,false,false));
	    // ------
	    
//	    printButton = new JButton("Print");
//	    printButton.addActionListener(new ActionListener() {
//			public void actionPerformed(ActionEvent e) {
//				print();
//			}
//		});
//	    p.add(printButton, AwtUtil.getConstraints(x++,y,1,0,1,1,false,true));
	    saveButton = new JButton("Save");
	    saveButton.addActionListener(new ActionListener() {
	    	public void actionPerformed(ActionEvent e) {
	    		save();
	    	}
	    });
	    p.add(saveButton, AwtUtil.getConstraints(x++,y,1,0,1,1,false,true));
	    closeButton = new JButton("Close");
	    closeButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				close();
			}
		});
	    p.add(closeButton, AwtUtil.getConstraints(x++,y++,1,0,1,1,false,true));
	    
	    getContentPane().add(p);

	    setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
	    enableEvents(WindowEvent.WINDOW_CLOSING);

	    setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
	    AwtUtil.centerWindow(this);

		System.out.println("Done My Position Report.");
	}

	/** Get the Report based on parameters given.
	 * Will be as HTML format. */
	private String getMainSummaryStr() {
//		System.out.println("getMainSummaryStr");
		StringBuffer sb = new StringBuffer();
		// Start
		sb.append("<HTML>");
		// Heading
		sb.append("<font size=5><strong>My Position Report</strong></font><br><br>");
		SimpleDateFormat pdf = new SimpleDateFormat("d MMM yyyy");
		sb.append("Todays Date: <strong>").append(pdf.format(today)).append("</strong><br>");
		sb.append("<br>");
		sb.append("Report from <strong>").append(pdf.format(startDate)).append("</strong> to <strong>").append(pdf.format(endDate)).append("</strong><br>");
		sb.append("Main Spending Account is <strong>").append(spendingAccountName).append("</strong>").append("<br>");
		sb.append("NOTE: Balances given here may not reflect true balance for spending account because report uses income and expenses from all accounts.").append("<br>");
		// Body

		sb.append("<table>");
		sb.append("<tr><td>&nbsp;</td><td><strong>Balance</strong></td><td><strong>Savings</strong></td></tr>");
		
		Long endBudgetBalanceL = (Long)reportObj.budgetBalanceMap.get(endDate);
		long endBudgetBalance = endBudgetBalanceL == null ? 0 : endBudgetBalanceL.longValue();

		// Start Balance
		sb.append("<tr><td>Start Balance</td><td>"+getCurrencyStr(spendingAccountStartBalance, null)+"</td></tr>");
		// Budget at end of report
		sb.append("<tr><td>Budget Goal on ").append(DT_FMT.format(endDate)).
			append("</td><td>").
			append(getCurrencyStr(endBudgetBalance,null)).append("</td><td>").
			append(getCurrencyStr(reportObj.budgetSavings,null)).append("</td></tr>");
		
		Long currBudgetBalanceL = (Long)reportObj.budgetBalanceMap.get(today);
		long currBudgetBalance = currBudgetBalanceL == null ? 0 : currBudgetBalanceL.longValue();
		Long currActualBalanceL = (Long)reportObj.actualBalanceMap.get(today);
		long currActualBalance = currActualBalanceL == null ? 0 : currActualBalanceL.longValue();
		long currDiff = currBudgetBalance - endBudgetBalance;
//		System.out.println("currBudgetBalance="+currBudgetBalance+
//				" currActualBalance="+currActualBalance+" currDiff="+currDiff);

		// Forecasted Savings
		long forecastBal = currActualBalance - currDiff;
		sb.append("<tr><td>Forecasted Actual Savings on ").append(DT_FMT.format(endDate)).
			append("</td><td>").
			append(getCurrencyStr(forecastBal,null)).append("</td><td>").
			append(getCurrencyStr(forecastBal - spendingAccountStartBalance,null)).append("</td></tr>");
		
		// Todays Budget Savings
		sb.append("<tr><td>Today Budget Savings").append("</td><td>").
			append(getCurrencyStr(currBudgetBalance,null)).append("</td><td>").
			append(getCurrencyStr(currBudgetBalance - reportObj.startBalance,null)).append("</td></tr>");
		
		// Todays Actual Savings
		sb.append("<tr><td>Today Actual Savings").append("</td><td>").
		append(getCurrencyStr(currActualBalance,null)).append("</td><td>").
		append(getCurrencyStr(currActualBalance - reportObj.startBalance,null)).append("</td></tr>");
		
		sb.append("</table>");
		sb.append("</HTML>");
		return sb.toString();
	}
	
	/**
	 * HTML string for income items
	 * @return
	 */
	private String getIncomeListStr() {
//		System.out.println("getIncomeListStr");
		StringBuffer sb = new StringBuffer();
		// Start
		sb.append("<HTML>");
		sb.append("<br><br><font size=5><strong>Income</strong></font><br>");
		
		// All Incomes
		sb.append("<table border=1>");
		sb.append("<tr><td><strong>Income Item</strong></td><td><strong>Actual Amount Today</strong></td><td><strong>Budget Amount Today</strong></td><td><strong>Diff Today</strong></td><td><strong>Budget ALL</strong></td></tr>");

		Map incomeItems = new HashMap(); // Map of name to IEReportItem
		// Actual Incomes
		for (Iterator it = reportObj.incomeActualItems.iterator(); it.hasNext();) {
			AmountItem item = (AmountItem) it.next();
			incomeItems.put(item.name, new IEReportItem(item.name,item.amount,0,0));
		}

		// Budget Incomes today
		for (Iterator it = reportObj.incomeBudgetItems.iterator(); it.hasNext();) {
			AmountItem item = (AmountItem) it.next();
			IEReportItem ier = (IEReportItem) incomeItems.get(item.name);
			if (ier == null) {
				incomeItems.put(item.name,new IEReportItem(item.name,0,item.amount,0));
			} else {
				ier.budgetTotalToDate = item.amount;
			}
		}
		
		// Budget Incomes ALL
		for (Iterator it = reportObj.incomeAllBudgetItems.iterator(); it.hasNext();) {
			AmountItem item = (AmountItem) it.next();
			IEReportItem ier = (IEReportItem) incomeItems.get(item.name);
			if (ier == null) {
				incomeItems.put(item.name,new IEReportItem(item.name,0,0,item.amount));
			} else {
				ier.budgetTotalAll = item.amount;
			}
		}
		
		// Put in a list so can sort
		List incomeList = new ArrayList();
		IEReportItem totalier = new IEReportItem("TOTAL",0,0,0);
		for (Iterator it = incomeItems.keySet().iterator(); it.hasNext();) {
			String name = (String) it.next();
			IEReportItem ier = (IEReportItem)incomeItems.get(name);
			incomeList.add(ier);
			totalier.actualTotal += ier.actualTotal;
			totalier.budgetTotalToDate += ier.budgetTotalToDate;
			totalier.budgetTotalAll += ier.budgetTotalAll;
		}
		incomeList.add(totalier);
		
		// Sort highest budget all to lowest
		Collections.sort(incomeList,new Comparator() {
			public int compare(Object arg0, Object arg1) {
				IEReportItem i0 = (IEReportItem) arg0;
				IEReportItem i1 = (IEReportItem) arg1;
				if (i0.name.equals("TOTAL")) return 1;
				if (i1.name.equals("TOTAL")) return -1;
				int ret = i0.budgetTotalAll < i1.budgetTotalAll ? 1 : 
					i0.budgetTotalAll == i1.budgetTotalAll ? 0 : -1;
				if (ret == 0) {
					ret = i0.budgetTotalToDate < i1.budgetTotalToDate ? 1 : 
						i0.budgetTotalToDate == i1.budgetTotalToDate ? 0 : -1;
				}
				if (ret == 0) {
					ret = i0.actualTotal < i1.actualTotal ? 1 : 
						i0.actualTotal == i1.actualTotal ? 0 : -1;
				}
				return ret;
			}
		});
		
		// Show in report
		for (Iterator it = incomeList.iterator(); it.hasNext();) {
			IEReportItem ier = (IEReportItem) it.next();
			String sss = "";
			String sse = "";
			if (ier.name.equals("TOTAL")) {
				sss = "<strong>";
				sse = "</strong>";
			}
			sb.append("<tr>");
			sb.append("<td>").append(sss).append(ier.name).append(sse).append("</td>");
			sb.append("<td align=\"right\">").append(sss).append(getCurrencyStr(ier.actualTotal,null)).append(sse).append("</td>");
			sb.append("<td align=\"right\">").append(sss).append(getCurrencyStr(ier.budgetTotalToDate,null)).append(sse).append("</td>");
			sb.append("<td align=\"right\">").append(sss).append(getCurrencyStr(ier.actualTotal - ier.budgetTotalToDate,null)).append(sse).append("</td>");
			sb.append("<td align=\"right\">").append(sss).append(getCurrencyStr(ier.budgetTotalAll,null)).append(sse).append("</td>");
			
			sb.append("</tr>");
		}
		
		sb.append("</table>");
		sb.append("<br><br>");
		sb.append("</HTML>");
		return sb.toString();
	}
	
	/**
	 * HTML String for Expense Items
	 * @return
	 */
	private String getExpenseListStr() {
//		System.out.println("getExpenseListStr");
		StringBuffer sb = new StringBuffer();
		// Start
		sb.append("<HTML>");
		sb.append("<br><br><font size=5><strong>Expense</strong></font><br>");
		
		// All Expenses
		sb.append("<table border=1>");
		sb.append("<tr><td><strong>Expense Item</strong></td><td><strong>Actual Amount Today</strong></td><td><strong>Budget Amount Today</strong></td><td><strong>Diff Today</strong></td><td><strong>Budget ALL</strong></td></tr>");
		
		Map expenseItems = new HashMap(); // Map of name to IEReportItem
		// Actual Expenses
		for (Iterator it = reportObj.expenseActualItems.iterator(); it.hasNext();) {
			AmountItem item = (AmountItem) it.next();
			expenseItems.put(item.name, new IEReportItem(item.name,item.amount,0,0));
		}
		
		// Budget Expense today
		for (Iterator it = reportObj.expenseBudgetItems.iterator(); it.hasNext();) {
			AmountItem item = (AmountItem) it.next();
			IEReportItem ier = (IEReportItem) expenseItems.get(item.name);
			if (ier == null) {
				expenseItems.put(item.name,new IEReportItem(item.name,0,item.amount,0));
			} else {
				ier.budgetTotalToDate = item.amount;
			}
		}
		
		// Budget Expenses ALL
		for (Iterator it = reportObj.expenseAllBudgetItems.iterator(); it.hasNext();) {
			AmountItem item = (AmountItem) it.next();
			IEReportItem ier = (IEReportItem) expenseItems.get(item.name);
			if (ier == null) {
				expenseItems.put(item.name,new IEReportItem(item.name,0,0,item.amount));
			} else {
				ier.budgetTotalAll = item.amount;
			}
		}
		
		// Put in a list so can sort
		List incomeList = new ArrayList();
		IEReportItem totalier = new IEReportItem("TOTAL",0,0,0);
		for (Iterator it = expenseItems.keySet().iterator(); it.hasNext();) {
			String name = (String) it.next();
			IEReportItem ier = (IEReportItem)expenseItems.get(name);
			incomeList.add(ier);
			totalier.actualTotal += ier.actualTotal;
			totalier.budgetTotalToDate += ier.budgetTotalToDate;
			totalier.budgetTotalAll += ier.budgetTotalAll;
		}
		incomeList.add(totalier);
		
		// Sort highest budget all to lowest
		Collections.sort(incomeList,new Comparator() {
			public int compare(Object arg0, Object arg1) {
				IEReportItem i0 = (IEReportItem) arg0;
				IEReportItem i1 = (IEReportItem) arg1;
				if (i0.name.equals("TOTAL")) return 1;
				if (i1.name.equals("TOTAL")) return -1;
				int ret = i0.budgetTotalAll < i1.budgetTotalAll ? -1 : 
					i0.budgetTotalAll == i1.budgetTotalAll ? 0 : 1;
				if (ret == 0) {
					ret = i0.budgetTotalToDate < i1.budgetTotalToDate ? -1 : 
						i0.budgetTotalToDate == i1.budgetTotalToDate ? 0 : 1;
				}
				if (ret == 0) {
					ret = i0.actualTotal < i1.actualTotal ? -1 : 
						i0.actualTotal == i1.actualTotal ? 0 : 1;
				}
				return ret;
			}
		});
		
		// Show in report
		for (Iterator it = incomeList.iterator(); it.hasNext();) {
			IEReportItem ier = (IEReportItem) it.next();
			String sss = "";
			String sse = "";
			if (ier.name.equals("TOTAL")) {
				sss = "<strong>";
				sse = "</strong>";
			}
			sb.append("<tr>");
			sb.append("<td>").append(sss).append(ier.name).append(sse).append("</td>");
			sb.append("<td align=\"right\">").append(sss).append(getCurrencyStr(-ier.actualTotal,null)).append(sse).append("</td>");
			sb.append("<td align=\"right\">").append(sss).append(getCurrencyStr(-ier.budgetTotalToDate,null)).append(sse).append("</td>");
			sb.append("<td align=\"right\">").append(sss).append(getCurrencyStr(-ier.budgetTotalToDate + ier.actualTotal,null)).append(sse).append("</td>");
			sb.append("<td align=\"right\">").append(sss).append(getCurrencyStr(-ier.budgetTotalAll,null)).append(sse).append("</td>");
			
			sb.append("</tr>");
		}
		
		sb.append("</table>");
		sb.append("<br><br>");
		sb.append("</HTML>");
		return sb.toString();
	}

	/**
	 * HTML String for every item day by day in given time period.
	 * @return
	 */
	private String getDayByDayReportStr() {
//		System.out.println("getDayByDayReportStr");
		StringBuffer sb = new StringBuffer();
		// Start
		sb.append("<HTML>");
		sb.append("<br><br><font size=5><strong>Day by Day Balance over Full Period</strong></font><br>");
		
		// All Days
		
		sb.append("<table border=1>");
		sb.append("<tr><td><strong>Date</strong></td><td><strong>Budget Item</strong></td><td><strong>Budget Balance</strong></td><td><strong>Actual Item</strong></td><td><strong>Actual Balance</strong></td><td><strong>Balance Diff</strong></td></tr>");
		Date dt = startDate;
		while (true) {
			sb.append("<tr><td>");
			// Budget Item
			sb.append(DT_FMT2.format(dt)).append("</td><td>");
			List bl = (List) reportObj.budgetItemListDayMap.get(dt);
			if (bl != null) {
				for (Iterator it = bl.iterator(); it.hasNext();) {
					DayItem di = (DayItem) it.next();
					sb.append(di.name+" "+getCurrencyStr(di.amount,null));
					if (it.hasNext()) sb.append(", ");
				}
			}
			sb.append("</td><td>");
			
			// Budget Balance
			long budgetBalance = reportObj.budgetBalanceMap.get(dt)==null?0:((Long)reportObj.budgetBalanceMap.get(dt)).longValue();
			sb.append(getCurrencyStr(budgetBalance,null));
			sb.append("</td><td>");
			
			// Actual Item
			List al = (List) reportObj.actualItemListDayMap.get(dt);
			if (al != null) {
				for (Iterator it = al.iterator(); it.hasNext();) {
					DayItem di = (DayItem) it.next();
					sb.append(di.name+" "+getCurrencyStr(di.amount,null));
					if (it.hasNext()) sb.append(", ");
				}
			}
			sb.append("</td><td align=\"right\">");
			
			// Actual Balance
			long actualBalance = reportObj.actualBalanceMap.get(dt)==null?0:((Long)reportObj.actualBalanceMap.get(dt)).longValue();
			sb.append(getCurrencyStr(actualBalance,null));
			sb.append("</td><td align=\"right\">");
			
			// Balance Diff
			sb.append(getCurrencyStr(actualBalance - budgetBalance,null));
			sb.append("</td></tr>");
			
			dt = DateUtil.addDays(dt, 1);
			
			if (dt.after(endDate)) {
				break;
			}
		}
		sb.append("</table>");
		// End
		sb.append("</HTML>");
//		System.out.println("Done getReportStr");
		return sb.toString();
	}
	
	/** Collect data used for the report */
	private ReportObj getReportObj() {
		System.out.println("getReportObj");
		reportObj = new ReportObj();
		
		// ============ ACTUAL AMOUNTS =============
		setReportActualAmounts();

		// ============ BUDGET AMOUNTS =============
		setReportBudgetAmounts();
	
		// Set Report Balances
		setReportBalances();
		
		return reportObj;
	}

	/** Set the report Actual amounts */
	private void setReportActualAmounts() {
//		System.out.println("setReportActualAmounts");
		// Work out Actual amounts during desired time period.
		TransactionSet txSet = extension.getUnprotectedContext().getRootAccount().getTransactionSet();
		
		// Loop through all transactions
		Map actualIncomeMap = new HashMap();
		Map actualExpenseMap = new HashMap();
		Enumeration e = txSet.getAllTransactions();
		for (; e.hasMoreElements(); ) {
			AbstractTxn t = (AbstractTxn)e.nextElement();
			if (t == null) continue;

			// Is this transaction in the range?
			Date dt = DateUtil.setTimeZero(new Date(t.getDate()));
			if (!DateUtil.isInRange(dt,startDate,endDate)) continue;
//			System.out.println("  Actual amount="+t.getValue()+" type="+t.getAccount());
			
			// Only accept income or expense accounts
			boolean incomeAccount = false;
			boolean expenseAccount = false;
			long amount = 0;
			if (t.getAccount() instanceof IncomeAccount) {
				amount = -t.getValue();
				incomeAccount = true;
			} else if (t.getAccount() instanceof ExpenseAccount) {
				amount = -t.getValue();
				expenseAccount = true;
			} else {
				continue;
			}
			long transAmount = amount;
			String accountName = t.getAccount() == null ? "" : t.getAccount().getAccountName();
			Long currAmount = (Long)reportObj.actualAmountDayMap.get(dt);
			if (currAmount != null) {
				amount += currAmount.longValue();
			}
			// Put in map
//			System.out.println("    "+dt+" amount="+amount+" trans.amount="+t.getValue());
			reportObj.actualAmountDayMap.put(dt, new Long(amount));
			reportObj.addActualItemListDayMap(new DayItem(dt, accountName,transAmount));
			
			// Income
			if (incomeAccount) {
				long incomeAmount = transAmount;
				Long currIncomeAmount = (Long)reportObj.incomeActualAmountDayMap.get(dt);
				if (currIncomeAmount != null) {
					incomeAmount += currIncomeAmount.longValue();
				}
				reportObj.incomeActualAmountDayMap.put(dt, new Long(incomeAmount));
				reportObj.incomeActualTotal += transAmount;
				long ii = transAmount;
				Long it = (Long)actualIncomeMap.get(accountName);
				if (it != null) {
					ii += it.longValue();
				}
				actualIncomeMap.put(accountName, new Long(ii));
			}
			
			// Expense
			if (expenseAccount) {
				long expenseAmount = transAmount;
				Long currExpenseAmount = (Long)reportObj.expenseActualAmountDayMap.get(dt);
				if (currExpenseAmount != null) {
					expenseAmount += currExpenseAmount.longValue();
				}
				reportObj.expenseActualAmountDayMap.put(dt, new Long(expenseAmount));
				reportObj.expenseActualTotal += transAmount;
				long ii = transAmount;
				Long it = (Long)actualExpenseMap.get(accountName);
				if (it != null) {
					ii += it.longValue();
				}
				actualExpenseMap.put(accountName, new Long(ii));
			}
		}
		
//		System.out.println("Add Income Items");
		// Income Items
		for (Iterator it = actualIncomeMap.keySet().iterator(); it.hasNext();) {
			String name = (String) it.next();
			Long amount = (Long) actualIncomeMap.get(name);
			reportObj.incomeActualItems.add(new AmountItem(name,amount.longValue()));
		}
		
//		System.out.println("Add Expense Items");
		// Expense Items
		for (Iterator it = actualExpenseMap.keySet().iterator(); it.hasNext();) {
			String name = (String) it.next();
			Long amount = (Long) actualExpenseMap.get(name);
			reportObj.expenseActualItems.add(new AmountItem(name,amount.longValue()));
		}
		
	}
	
	/** Set the report Budget amounts */
	private void setReportBudgetAmounts() {
//		System.out.println("setReportBudgetAmounts");
		Map budgetIncomeMap = new HashMap();
		Map budgetExpenseMap = new HashMap();
		Map allBudgetIncomeMap = new HashMap();
		Map allBudgetExpenseMap = new HashMap();
		// Loop through all budgeted items
		// If more than one budget has the same Category budgeted, it
		//will use the last budgets value (Should be same really anyway)
		List budgetCategories = new ArrayList();
		BudgetList budList = extension.getUnprotectedContext().getRootAccount().getBudgetList();
		for (int i = 0; i < budList.getBudgetCount(); i++) {
			Budget b = budList.getBudget(i);
			for (int j = 0; j < b.getItemCount(); j++) {
				BudgetItem bi = b.getItem(j);
				Account a = bi.getTransferAccount();
				String budKey = getBudgetCatKey(bi);
				// Have we done this category already?
				if (budgetCategories.contains(budKey)) continue;
				budgetCategories.add(budKey);
//				System.out.println("  account="+a.getAccountName());
				// TODO Store budget amount with budget startDate ???
				
				// Only accept income or expense accounts
				long budAmount = 0;
				boolean incomeAccount = false;
				boolean expenseAccount = false;
				if (a instanceof IncomeAccount) {
					 budAmount = bi.getAmount();
					 incomeAccount = true;
				} else if (a instanceof ExpenseAccount){
					budAmount = -bi.getAmount();
					expenseAccount = true;
				} else {
					continue;
				}
				
				Date budStartDate = DateUtil.getDateYYYYMMDD(bi.getIntervalStartDate());
				Date budEndDate = null;
				if (bi.getIntervalEndDate() != 0) {
					budEndDate = DateUtil.getDateYYYYMMDD(bi.getIntervalEndDate());
				}
				Date budDt = budStartDate;
//				System.out.println("  IntStart="+bi.getIntervalStartDate()+" intEnd="+bi.getIntervalEndDate());
//				System.out.println("  BudStart="+budStartDate+" BudEnd="+budEndDate);
				
				// Is it after end day?
				if (budStartDate == null || budStartDate.after(endDate)) continue;
				// Is it before start day
				if (budEndDate != null && budEndDate.before(startDate)) continue;
				
				boolean done = false;
				while (!done) {
					// Is budget in range
					if (DateUtil.isInRange(budDt, startDate, endDate)) {
						long amount = budAmount;
						Long currAmount = (Long)reportObj.budgetAmountDayMap.get(budDt);
						if (currAmount != null) {
							amount += currAmount.longValue();
						}
						// Put in map
						reportObj.budgetAmountDayMap.put(budDt, new Long(amount));
						// Income only
						if (incomeAccount) {
							long incomeAmount = budAmount;
							Long currIncomeAmount = (Long)reportObj.incomeBudgetAmountDayMap.get(budDt);
							if (currIncomeAmount != null) {
								incomeAmount += currIncomeAmount.longValue();
							}
							// Put in map
							reportObj.incomeBudgetAmountDayMap.put(budDt, new Long(incomeAmount));
							if (DateUtil.isInSameDayOrBefore(budDt, today)) {
								long ii = budAmount;
								Long it = (Long)budgetIncomeMap.get(a.getAccountName());
								if (it != null) {
									ii += it.longValue();
								}
								budgetIncomeMap.put(a.getAccountName(), new Long(ii));
								reportObj.incomeBudgetTotal += budAmount;
							}
							// All Budget incomes (ie for year)
							long ii = budAmount;
							Long it = (Long)allBudgetIncomeMap.get(a.getAccountName());
							if (it != null) {
								ii += it.longValue();
							}
							allBudgetIncomeMap.put(a.getAccountName(), new Long(ii));
							reportObj.incomeAllBudgetTotal += budAmount;
						}
						// Expense only
						if (expenseAccount) {
							long expenseAmount = budAmount;
							Long currExpenseAmount = (Long)reportObj.expenseBudgetAmountDayMap.get(budDt);
							if (currExpenseAmount != null) {
								expenseAmount += currExpenseAmount.longValue();
							}
							// Put in map
							reportObj.expenseBudgetAmountDayMap.put(budDt, new Long(expenseAmount));
							if (DateUtil.isInSameDayOrBefore(budDt, today)) {
								long ii = budAmount;
								Long it = (Long)budgetExpenseMap.get(a.getAccountName());
								if (it != null) {
									ii += it.longValue();
								}
								budgetExpenseMap.put(a.getAccountName(), new Long(ii));
								reportObj.expenseBudgetTotal += budAmount;
							}
							// All Budget expenses (ie for year)
							long ii = budAmount;
							Long it = (Long)allBudgetExpenseMap.get(a.getAccountName());
							if (it != null) {
								ii += it.longValue();
							}
							allBudgetExpenseMap.put(a.getAccountName(), new Long(ii));
							reportObj.expenseAllBudgetTotal += budAmount;
						}
						
//						System.out.println("--add--"+budDt+" amount="+amount);
						reportObj.addBudgetItemListDayMap(new DayItem(budDt, bi.getTransferAccount().getAccountName(),budAmount));
					}
					// Get next budgeted date
					switch (bi.getInterval()) {
						case BudgetItem.INTERVAL_ANNUALLY: budDt = DateUtil.addYears(budDt, 1); break;
						case BudgetItem.INTERVAL_BI_MONTHLY: budDt = DateUtil.addMonths(budDt, 2); break;
						case BudgetItem.INTERVAL_BI_WEEKLY: budDt = DateUtil.addWeeks(budDt,2); break;
						case BudgetItem.INTERVAL_DAILY:budDt = DateUtil.addDays(budDt,1);break;
						case BudgetItem.INTERVAL_MONTHLY:budDt = DateUtil.addMonths(budDt,1);break;
						case BudgetItem.INTERVAL_SEMI_ANNUALLY: {
							if (DateUtil.isSameDayOfYear(budDt,budStartDate)) {
								budDt = DateUtil.addDays(budDt,182);
							} else {
								budDt = DateUtil.addDays(budDt,-182);
								budDt =DateUtil.addYears(budDt, 1);
							}
							break;
						}
						case BudgetItem.INTERVAL_SEMI_MONTHLY: {
							if (DateUtil.isSameDayOfMonth(budDt,budStartDate)) {
								budDt = DateUtil.addDays(budDt,15);
							} else {
								budDt = DateUtil.addDays(budDt,-15);
								budDt =DateUtil.addMonths(budDt, 1);
							}
							break;
						}
						case BudgetItem.INTERVAL_TRI_MONTHLY: budDt = DateUtil.addMonths(budDt, 3); break;
						case BudgetItem.INTERVAL_TRI_WEEKLY: budDt = DateUtil.addWeeks(budDt,3); break;
						case BudgetItem.INTERVAL_WEEKLY: budDt = DateUtil.addWeeks(budDt,1); break;
						default: done = true;
					}
					
					// Is it past date
					if (budDt.after(endDate) || (budEndDate != null && budDt.after(budEndDate))) {
						break;
					}
				}
			}
		}
		
//		System.out.println("Add Income Items");
		// Income Items
		for (Iterator it = budgetIncomeMap.keySet().iterator(); it.hasNext();) {
			String name = (String) it.next();
			Long amount = (Long) budgetIncomeMap.get(name);
			reportObj.incomeBudgetItems.add(new AmountItem(name,amount.longValue()));
		}
		for (Iterator it = allBudgetIncomeMap.keySet().iterator(); it.hasNext();) {
			String name = (String) it.next();
			Long amount = (Long) allBudgetIncomeMap.get(name);
			reportObj.incomeAllBudgetItems.add(new AmountItem(name,amount.longValue()));
		}
		
//		System.out.println("Add Expense Items");
		// Expense Items
		for (Iterator it = budgetExpenseMap.keySet().iterator(); it.hasNext();) {
			String name = (String) it.next();
			Long amount = (Long) budgetExpenseMap.get(name);
			reportObj.expenseBudgetItems.add(new AmountItem(name,amount.longValue()));
		}
		for (Iterator it = allBudgetExpenseMap.keySet().iterator(); it.hasNext();) {
			String name = (String) it.next();
			Long amount = (Long) allBudgetExpenseMap.get(name);
			reportObj.expenseAllBudgetItems.add(new AmountItem(name,amount.longValue()));
		}

	}
	
	/**
	 * Budget Category Key
	 * @param bi Budget Item
	 * @return
	 */
	private String getBudgetCatKey(BudgetItem bi) {
		if (bi == null) return "";
		String key = (bi.getTransferAccount() == null ? "" : bi.getTransferAccount().getAccountName()) + "|" + 
				bi.getIntervalStartDate();
		return key;
	}

	/** Set report budgeted and actual balances */
	private void setReportBalances() {
//		System.out.println("setReportBalances");
		Date dt = startDate;
		
		long actualBalance = spendingAccountStartBalance;
		long startBalance = actualBalance;
		reportObj.startBalance = startBalance;
		long budgetBalance = actualBalance;
//		System.out.println("  startBalance="+startBalance);
		// loop through all days
		int count = 0;
		while (true) {
			// Actual value for this day
			Long dayActual = (Long)reportObj.actualAmountDayMap.get(dt);
			if (dayActual != null) {
				actualBalance += dayActual.longValue();
			}
			reportObj.actualBalanceMap.put(dt,new Long(actualBalance));
			
			// Budget value for this day
			Long dayBudget = (Long)reportObj.budgetAmountDayMap.get(dt);
			if (dayBudget != null) {
				budgetBalance += dayBudget.longValue();
			}
			reportObj.budgetBalanceMap.put(dt,new Long(budgetBalance));
			
			// Add date, reached end?
			dt = DateUtil.addDays(dt, 1);
			if (dt.after(endDate)) {
				break;
			}
			count++;
//			System.out.println(count+" "+dt+" budgetBal="+budgetBalance+" actualBal="+actualBalance);
		}
		reportObj.actualSavings = actualBalance - startBalance;
		reportObj.budgetSavings = budgetBalance - startBalance;
//		System.out.println("  actualSavings="+reportObj.actualSavings);
//		System.out.println("  budgetSavings="+reportObj.budgetSavings);
	}
	
	/** Get balance for spending account */
	public long getSpendingAccountBalance(Date adt) {
//		System.out.println("getSpendingAccountBalance dt="+adt+" bal="+spendingAccount.getBalance());
		long startBalance = spendingAccount.getStartBalance();
		long bal = startBalance;
		
		TransactionSet txSet = extension.getUnprotectedContext().getRootAccount().getTransactionSet();
		
		// Loop through all transactions
		Enumeration e = txSet.getAllTransactions();
		for (; e.hasMoreElements(); ) {
			AbstractTxn t = (AbstractTxn)e.nextElement();
			if (t == null) continue;
			// Make sure correct account
			if (!t.getAccount().getAccountName().equalsIgnoreCase(spendingAccountName)) continue;

			// Is this transaction in the range?
			Date dt = DateUtil.setTimeZero(new Date(t.getDate()));
			// Only interested in those dates before date given 
			if (DateUtil.isAfter(dt,adt)) continue;

			bal += t.getValue();
//			System.out.println("    amount="+t.getValue()+"  account="+
//					t.getAccount().getAccountName()+
//					" parent account="+t.getAccount().getParentAccount().getAccountName()+
//					" dt="+dt+" adt="+adt+" type="+t.getAccount()+" bal="+bal);
		}
		
		return bal;
	}
	
	/**
	 * Chart dataset for Balance Chart
	 * @return
	 */
	private TimeSeriesCollection createBalanceDataset() {
//		System.out.println("createBalanceDataset");
		final TimeSeries seriesBudget = new TimeSeries("Budget",Day.class);
        final TimeSeries seriesActual = new TimeSeries("Actual",Day.class);
        final TimeSeries seriesActualForecast = new TimeSeries("Actual Forecast",Day.class);

		Date dt = startDate;
		
		long actualBalance = spendingAccountStartBalance;
		long budgetBalance = actualBalance;
//		System.out.println("  balance="+actualBalance);
//		System.out.println("  repoObj="+reportObj);
		// loop through all days
		int count = 0;
		long todayOffset = 0;
		while (true) {
			// Actual value for this day
			Long dayActual = (Long)reportObj.actualAmountDayMap.get(dt);
			if (dayActual != null) {
				actualBalance += dayActual.longValue();
			}
			
			// Budget value for this day
			Long dayBudget = (Long)reportObj.budgetAmountDayMap.get(dt);
			if (dayBudget != null) {
				budgetBalance += dayBudget.longValue();
			}
			seriesBudget.add(new Day(dt),((double)budgetBalance/100));
			if (DateUtil.isInSameDay(dt, today)) {
				todayOffset = actualBalance - budgetBalance;
			}
			if (DateUtil.isAfter(dt, today)) {
				seriesActualForecast.add(new Day(dt),((double)(budgetBalance+todayOffset)/100));
			} else {
				seriesActual.add(new Day(dt),((double)actualBalance/100));
			}
			
			// Add date, reached end?
			dt = DateUtil.addDays(dt, 1);
			if (dt.after(endDate)) {
				break;
			}
			count++;
		}
        
        final TimeSeriesCollection dataset = new TimeSeriesCollection();
        dataset.addSeries(seriesBudget);
        dataset.addSeries(seriesActual);
        dataset.addSeries(seriesActualForecast);
                
        return dataset;
	}
	
	private JFreeChart createBalanceChart(TimeSeriesCollection dataset, String title) {
//		System.out.println("createBalanceChart "+title);
		  // create the chart...
        final JFreeChart chart = ChartFactory.createTimeSeriesChart(
            title,      // chart title
            "Date",                      // x axis label
            "Balance $",                      // y axis label
            dataset,                  // data
//            PlotOrientation.VERTICAL,
            true,                     // include legend
            true,                     // tooltips
            false                     // urls
        );

        // NOW DO SOME OPTIONAL CUSTOMISATION OF THE CHART...
        chart.setBackgroundPaint(Color.white);

        // get a reference to the plot for further customisation...
        final XYPlot plot = chart.getXYPlot();
        plot.setBackgroundPaint(Color.white);
        plot.setDomainGridlinePaint(Color.gray);
        plot.setRangeGridlinePaint(Color.gray);
        
        final XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer();
        renderer.setSeriesShapesVisible(0, false);
        renderer.setSeriesShapesVisible(1, false);
        renderer.setSeriesShapesVisible(2, false);
        plot.setRenderer(renderer);

        // change the auto tick unit selection to integer units only...
        final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
        rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
        // OPTIONAL CUSTOMISATION COMPLETED.
                
        return chart;
	}
	
	private TimeSeriesCollection createIncomeDataset() {
//		System.out.println("createIncomeDataset");
		final TimeSeries seriesBudget = new TimeSeries("Budget",Day.class);
        final TimeSeries seriesActual = new TimeSeries("Actual",Day.class);
        final TimeSeries seriesActualForecast = new TimeSeries("Actual Forecast",Day.class);

		Date dt = startDate;
		
		long actualBalance = 0;
		long budgetBalance = actualBalance;
		// loop through all days
		int count = 0;
		long todayOffset = 0;
		while (true) {
			// Actual value for this day
			Long dayActual = (Long)reportObj.incomeActualAmountDayMap.get(dt);
			if (dayActual != null) {
				actualBalance += dayActual.longValue();
			}
			
			// Budget value for this day
			Long dayBudget = (Long)reportObj.incomeBudgetAmountDayMap.get(dt);
			if (dayBudget != null) {
				budgetBalance += dayBudget.longValue();
			}
			seriesBudget.add(new Day(dt),((double)budgetBalance/100));
			if (DateUtil.isInSameDay(dt, today)) {
				todayOffset = actualBalance - budgetBalance;
			}
			if (DateUtil.isAfter(dt, today)) {
				seriesActualForecast.add(new Day(dt),((double)(budgetBalance+todayOffset)/100));
			} else {
				seriesActual.add(new Day(dt),((double)actualBalance/100));
			}
			
			// Add date, reached end?
			dt = DateUtil.addDays(dt, 1);
			if (dt.after(endDate)) {
				break;
			}
			count++;
		}
        
        final TimeSeriesCollection dataset = new TimeSeriesCollection();
        dataset.addSeries(seriesBudget);
        dataset.addSeries(seriesActual);
        dataset.addSeries(seriesActualForecast);
                
        return dataset;
	}
	
	private TimeSeriesCollection createExpenseDataset() {
//		System.out.println("createExpenseDataset");
		final TimeSeries seriesBudget = new TimeSeries("Budget",Day.class);
		final TimeSeries seriesActual = new TimeSeries("Actual",Day.class);
		final TimeSeries seriesActualForecast = new TimeSeries("Actual Forecast",Day.class);
		
		Date dt = startDate;
		
		long actualBalance = 0;
		long budgetBalance = actualBalance;
		// loop through all days
		int count = 0;
		long todayOffset = 0;
		while (true) {
			// Actual value for this day
			Long dayActual = (Long)reportObj.expenseActualAmountDayMap.get(dt);
			if (dayActual != null) {
				actualBalance += dayActual.longValue();
			}
			
			// Budget value for this day
			Long dayBudget = (Long)reportObj.expenseBudgetAmountDayMap.get(dt);
			if (dayBudget != null) {
				budgetBalance += dayBudget.longValue();
			}
			seriesBudget.add(new Day(dt),((double)budgetBalance/100));
			if (DateUtil.isInSameDay(dt, today)) {
				todayOffset = actualBalance - budgetBalance;
			}
			if (DateUtil.isAfter(dt, today)) {
				seriesActualForecast.add(new Day(dt),((double)(budgetBalance+todayOffset)/100));
			} else {
				seriesActual.add(new Day(dt),((double)actualBalance/100));
			}
			
			// Add date, reached end?
			dt = DateUtil.addDays(dt, 1);
			if (dt.after(endDate)) {
				break;
			}
			count++;
		}
		
		final TimeSeriesCollection dataset = new TimeSeriesCollection();
		dataset.addSeries(seriesBudget);
		dataset.addSeries(seriesActual);
		dataset.addSeries(seriesActualForecast);
		
		return dataset;
	}
	
	private PieDataset createIncomeExpenseDataset(List items, long total, final boolean income) {
//		System.out.println("createIncomeExpenseDataset total="+total);
        final DefaultPieDataset result = new DefaultPieDataset();
        
        Collections.sort(items, new Comparator() {
			public int compare(Object o1, Object o2) {
				AmountItem a1 = (AmountItem) o1;
				AmountItem a2 = (AmountItem) o2;
				int r = income ? -1 : 1;
				return a1.amount > a2.amount ? r : a1.amount == a2.amount ? 0 : -r;
			}
		});
        
        int count = 0;
        long otherTotal = 0;
        for (Iterator it = items.iterator(); it.hasNext();) {
			AmountItem item = (AmountItem) it.next();
			double value = item.amount * 100 / total;
			if (count < 16) {
				result.setValue(item.name+" "+getCurrencyStrPlain(item.amount)+" ("+getPercStr(item.amount,total)+"%)", value);
			} else {
				otherTotal += item.amount;
			}
			
			count++;
		}
        if (count >= 16) {
        	result.setValue("Other "+getCurrencyStrPlain(otherTotal)+" ("+getPercStr(otherTotal,total)+"%)", otherTotal * 100 / total);
        }
        
        return result;	
    }
	
	/**
	 * Percent String shown in Pie Charts
	 * @param val Value
	 * @param total Total Value
	 * @return
	 */
	private String getPercStr(double val,double total) {
		NumberFormat nf = new DecimalFormat("#0.0");
		return nf.format(val*100/total);
	}
	
	private JFreeChart createIncomeExpensePieChart(PieDataset dataset, String title) {
		// create the chart...
        final JFreeChart chart = ChartFactory.createPieChart(
            title,  // chart title
            dataset,             // dataset
            false,               // include legend
            true,
            false
        );

        // set the background color for the chart...
        chart.setBackgroundPaint(new Color(222, 222, 255));
        final PiePlot plot = (PiePlot) chart.getPlot();
        plot.setBackgroundPaint(Color.white);
        plot.setCircular(true);
        plot.setNoDataMessage("No data available");
        return chart;
	}
	
	/** Return amount as dollars and cents in HTML format. 
	 * If date in future return empty space.*/
	public String getCurrencyStr(long amount, Date dt) {
		StringBuffer sb = new StringBuffer();
		if (dt != null && dt.after(new Date()) && amount == 0) {
			return "&nbsp;";
		}
		if (amount < 0) sb.append("<font color=\"red\">");
		sb.append(CURR_FMT.format(amount/100)).append(".").append(CENTS_FMT.format(Math.abs(amount%100)));
		if (amount < 0) sb.append("</font>");
		return sb.toString();
	}
	public String getCurrencyStrPlain(long amount) {
		StringBuffer sb = new StringBuffer();
		sb.append(CURR_FMT.format(amount/100)).append(".").append(CENTS_FMT.format(Math.abs(amount%100)));
		return sb.toString();
	}
	
	/** Save the Report */
	protected void save() {
		//Create a file chooser
		JFileChooser fc = new JFileChooser();

		File defFile = new File("MyPosition"+DT_FMT.format(today)+".html");
		fc.setSelectedFile(defFile);
		
		//In response to a button click:
		int returnVal = fc.showSaveDialog(this);
		
		if (returnVal == JFileChooser.APPROVE_OPTION) {
            File file = fc.getSelectedFile();

            saveFile(file);
        }
	}
	
	protected void saveFile(File file) {
		String folder = file.getParentFile().getPath()+File.separator;
        try {
            BufferedWriter out = new BufferedWriter(new FileWriter(file));
            out.write(txtMainSummary.getText());
            saveChart(out,balanceChart,folder,"BalanceChart");
            out.write(txtIncomes.getText());
            saveChart(out,incomeChart,folder,"IncomeChart");
            saveChart(out,incomeActualPieChart,folder,"IncomeActualPieChart");
            saveChart(out,incomeBudgetPieChart,folder,"IncomeBudgetPieChart");
            out.write(txtExpenses.getText());
            saveChart(out,expenseChart,folder,"ExpenseChart");
            saveChart(out,expenseActualPieChart,folder,"ExpenseActualPieChart");
            saveChart(out,expenseBudgetPieChart,folder,"ExpenseBudgetPieChart");
            saveChart(out,expenseAllBudgetPieChart,folder,"ExpenseAllBudgetPieChart");
    	    if (this.includeDayByDayBalance) {
    	    	out.write(txtReport.getText());
    	    }

            out.close();
        } catch (IOException e) {
        }
	}

	/**
	 * Save the chart as a Image File and put in HTML String.
	 * @param out
	 * @param chart
	 * @param folder
	 * @param chartName
	 * @throws IOException
	 */
	protected void saveChart(BufferedWriter out,JFreeChart chart,String folder,String chartName) throws IOException {
		// Save chart as a file
		String filename = "MyPosition_"+chartName+DT_FMT.format(today)+".png";
		BufferedImage bi = chart.createBufferedImage(WINDOW_CHART_WIDTH, WINDOW_BALANCE_HEIGHT);
		saveImageAsFile(bi,folder+filename);
		out.write("<br>");
		out.write("<img src=\""+filename+"\">");
		out.write("<br>");
	}
	
	public void saveImageAsFile(BufferedImage bi, String filename) throws IOException {
		ImageIO.write(bi, "png", new File(filename));
	}

	/**
	 * Print Report
	 */
	protected void print() {
		
		DocumentRenderer dr = new DocumentRenderer();
		
		dr.print(txtMainSummary);
//		dr.print(balanceChartPanel);
        dr.print(txtIncomes);
        dr.print(txtExpenses);
	    if (this.includeDayByDayBalance) {
	    	dr.print(txtReport);
	    }
	}
	
	/** Close Window */
	protected void close() {
		this.setVisible(false);
		this.dispose();
	}
	
	class ReportObj {
		/** Map of date to budget amount for day */ 
		public Map budgetAmountDayMap = new HashMap();
		/** Map of date to actual amount for day */
		public Map actualAmountDayMap = new HashMap();
		/** Map of date to budget items for day */ 
		public Map budgetItemListDayMap = new HashMap();
		/** Map of date to actual items for day */
		public Map actualItemListDayMap = new HashMap();
		/** Map of balance for budgeted items */
		public Map budgetBalanceMap = new HashMap();
		/** Map of balance for actual items */
		public Map actualBalanceMap = new HashMap();
		// Income
		/** Map of date to budget amount for day */ 
		public Map incomeBudgetAmountDayMap = new HashMap();
		/** Map of date to actual amount for day */
		public Map incomeActualAmountDayMap = new HashMap();
		/** List of Actual Amount items for income */
		public List incomeActualItems = new ArrayList();
		long incomeActualTotal = 0;
		/** List of Budget Amount items for income to today*/
		public List incomeBudgetItems = new ArrayList();
		long incomeBudgetTotal = 0;
		/** List of Budget Amount items for all incomes */
		public List incomeAllBudgetItems = new ArrayList();
		long incomeAllBudgetTotal = 0;
		// Expense
		/** Map of date to budget amount for day */ 
		public Map expenseBudgetAmountDayMap = new HashMap();
		/** Map of date to actual amount for day */
		public Map expenseActualAmountDayMap = new HashMap();
		/** List of Actual Amount items for expense */
		public List expenseActualItems = new ArrayList();
		long expenseActualTotal = 0;
		/** List of Budget Amount items for expense to today */
		public List expenseBudgetItems = new ArrayList();
		long expenseBudgetTotal = 0;
		/** List of Budget Amount items for all expenses */
		public List expenseAllBudgetItems = new ArrayList();
		long expenseAllBudgetTotal = 0;
		
		public long startBalance = 0;
		public long actualSavings = 0;
		public long budgetSavings = 0;
		
		public void addBudgetItemListDayMap(DayItem dayItem) {
			List l = (List) budgetItemListDayMap.get(dayItem.date);
			if (l == null) {
				l = new ArrayList();
				budgetItemListDayMap.put(dayItem.date, l);
			}
			l.add(dayItem);
		}
		
		public void addActualItemListDayMap(DayItem dayItem) {
			List l = (List) actualItemListDayMap.get(dayItem.date);
			if (l == null) {
				l = new ArrayList();
				actualItemListDayMap.put(dayItem.date, l);
			}
			l.add(dayItem);
		}
	}

	class DayItem {
		public Date date = null;
		public String name = null;
		public long amount = 0;
		
		public DayItem(Date date, String name, long amount) {
			this.date = date;
			this.name = name;
			this.amount = amount;
		}
	}
	
	class AmountItem {
		public String name = null;
		public long amount = 0;
		
		public AmountItem(String name, long amount) {
			this.name = name;
			this.amount = amount;
		}
	}
	
	class IEReportItem {
		public String name;
		public long actualTotal = 0;
		public long budgetTotalToDate = 0;
		public long budgetTotalAll = 0;

		public IEReportItem(String name, long actualTotal, long budgetTotalToDate, long budgetTotalAll) {
			this.name = name;
			this.actualTotal = actualTotal;
			this.budgetTotalToDate = budgetTotalToDate;
			this.budgetTotalAll = budgetTotalAll;
		}
		
	}

}
  